/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: sb16 driver
 *           only support play sound!
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2023-01-06     JasonHu           Init
 */

#define NX_LOG_NAME "sb16"
#include <base/log.h>

#ifdef CONFIG_NX_DRIVER_SB16

#include <base/driver.h>
#include <base/memory.h>
#include <base/time.h>
#include <base/irq.h>
#include <base/page.h>
#include <base/thread.h>
#include <base/uaccess.h>
#include <base/fifo.h>
#include <base/malloc.h>
#include <drvfw/audio.h>
#include <io.h>

#include "sb16.h"

#define DRV_NAME "sb16 device"
#define DEV_NAME "sb16"

#define SB16_BUF_COUNT  16

#define SB16_READ_DMA   1    /* record sound */
#define SB16_WRITE_DMA  2    /* play sound */

NX_PRIVATE void dsp_dma_setup(NX_Addr address, int count, int sub_dev);

NX_PRIVATE int dsp_set_size(NX_U32 size);
NX_PRIVATE int dsp_set_speed(NX_U32 speed);
NX_PRIVATE int dsp_set_stereo(NX_U32 stereo);
NX_PRIVATE int dsp_set_bits(NX_U32 bits);
NX_PRIVATE int dsp_set_sign(NX_U32 sign);

struct sb16_device
{
    NX_U32 dsp_stereo;
    NX_U32 dsp_speed;
    NX_U32 dsp_bits;
    NX_U32 dsp_sign;
    NX_U32 dsp_fragment_size;

    NX_U32 dma_size;
    NX_U32 nr_of_dma_fragments;
    NX_U32 min_fragment_size;

    NX_Addr dma_phys;

    int data_len[SB16_BUF_COUNT];
    char *data_buf;
    int index_w;
    int index_r;

    NX_Bool running;
};

NX_PRIVATE struct sb16_device * sb16 = NX_NULL;

/* Write a value to the DSP write register */
NX_PRIVATE void sb16_dsp_write(NX_U8 value)
{
    while (IO_In8(DSP_STATUS_WRITE) & 0x80)
    {
    }
    IO_Out8(DSP_WRITE, value);
}

/* Reads the value of the DSP read register */
NX_PRIVATE NX_U8 sb16_dsp_read()
{
    while (!(IO_In8(DSP_DATA_AVL) & 0x80))
    {
    }
    return IO_In8(DSP_READ);
}

NX_PRIVATE void mixer_set(int reg, int data)
{
	int i;

	IO_Out8(MIXER_REG, reg);
	for(i = 0; i < 1000; i++);
	IO_Out8(MIXER_DATA, data);
}

NX_PRIVATE int mixer_get(int reg) {
	int i;

	IO_Out8(MIXER_REG, reg);
	for(i = 0; i < 100; i++)
    {
    }
	return IO_In8(MIXER_DATA) & 0xff;
}  

NX_PRIVATE NX_Error sb16_reset(void)
{
    int i;

    IO_Out8(DSP_RESET, 1);
    /* wait 3 ms */
    for (i = 0; i < 3 * 1000; i++)
    {
        IO_In8(DSP_READ); /* io port in spend 1us */
    }
    IO_Out8(DSP_RESET, 0);
    
    for (i = 0; i < 1000; i++)
    {
        if (IO_In8(DSP_DATA_AVL) & 0x80)
        {
            break;
        }
    }

    if (IO_In8(DSP_READ) != 0xaa)
    {
        return NX_EIO;
    }
    return NX_EOK;
}

NX_PRIVATE int sb16_transmit(int DmaMode)
{
    char *p = &sb16->data_buf[sb16->index_r * sb16->dma_size];
    int len = sb16->data_len[sb16->index_r];

    if (sb16->index_w == sb16->index_r)
    {
        return -1;
    }

    /* copy buf to dma buf */
    NX_MemCopy((void *)NX_Phy2Virt(sb16->dma_phys), p, len);

    dsp_set_size(len / sb16->nr_of_dma_fragments);

	dsp_dma_setup(sb16->dma_phys, sb16->dsp_fragment_size * sb16->nr_of_dma_fragments, DmaMode);

	dsp_set_speed(sb16->dsp_speed);

	/* Put the speaker on */
	if (DmaMode == SB16_WRITE_DMA)
    {
		sb16_dsp_write (DSP_CMD_SPKON); /* put speaker on */

		/* Program DSP with dma mode */
		sb16_dsp_write((sb16->dsp_bits == 8 ? DSP_CMD_8BITAUTO_OUT : DSP_CMD_16BITAUTO_OUT));     
	}
    else
    {
		sb16_dsp_write (DSP_CMD_SPKOFF); /* put speaker off */

		/* Program DSP with dma mode */
		sb16_dsp_write((sb16->dsp_bits == 8 ? DSP_CMD_8BITAUTO_IN : DSP_CMD_16BITAUTO_IN));     
	}

	/* Program DSP with transfer mode */
	if (!sb16->dsp_sign)
    {
		sb16_dsp_write((sb16->dsp_stereo == 1 ? DSP_MODE_STEREO_US : DSP_MODE_MONO_US));
	}
    else
    {
		sb16_dsp_write((sb16->dsp_stereo == 1 ? DSP_MODE_STEREO_S : DSP_MODE_MONO_S));
	}

	/* Give length of fragment to DSP */
	if (sb16->dsp_bits == 8)
    { /* 8 bit transfer */
		/* #bytes - 1 */
		sb16_dsp_write((sb16->dsp_fragment_size - 1) >> 0); 
		sb16_dsp_write((sb16->dsp_fragment_size - 1) >> 8);
	}
    else
    {             /* 16 bit transfer */
		/* #words - 1 */
		sb16_dsp_write((sb16->dsp_fragment_size - 1) >> 1);
		sb16_dsp_write((sb16->dsp_fragment_size - 1) >> 9);
	}

	sb16->running = NX_True;

	return NX_EOK;
}

NX_PRIVATE int sb16_reenable_int(void)
{
	IO_In8((sb16->dsp_bits == 8 ? DSP_DATA_AVL : DSP_DATA16_AVL));
	return NX_EOK;
}

NX_PRIVATE int sb16_set_dma(NX_U32 dma)
{
	sb16->dma_phys = dma;
	return NX_EOK;
}

NX_PRIVATE int sb16_int(void)
{
	return mixer_get(MIXER_IRQ_STATUS) & 0x03;
}

NX_PRIVATE int sb16_pause(void)
{
	if (sb16->running)
    {
		sb16_dsp_write((sb16->dsp_bits == 8 ? DSP_CMD_DMA8HALT : DSP_CMD_DMA16HALT));
		sb16->running = NX_False;
		sb16_reenable_int();
	}
	return NX_EOK;
}

#if 0
/* if support resume */
NX_PRIVATE void sb16_resume(void)
{
	sb16_dsp_write((sb16->dsp_bits == 8 ? DSP_CMD_DMA8CONT : DSP_CMD_DMA16CONT));
}
#endif

NX_PRIVATE void dsp_dma_setup(NX_Addr address, int count, int DmaMode)
{
	if (sb16->dsp_bits == 8){   /* 8 bit sound */
		count--;     

		IO_Out8(DMA8_MASK, SB_DMA_8 | 0x04);      /* Disable DMA channel */
		IO_Out8(DMA8_CLEAR, 0x00);		       /* Clear flip flop */

		/* set DMA mode */
		IO_Out8(DMA8_MODE, (DmaMode == SB16_WRITE_DMA ? DMA8_AUTO_PLAY : DMA8_AUTO_REC)); 

		IO_Out8(DMA8_ADDR, (NX_U8)(address >>  0)); /* Low_byte of address */
		IO_Out8(DMA8_ADDR, (NX_U8)(address >>  8)); /* High byte of address */
		IO_Out8(DMA8_PAGE, (NX_U8)(address >> 16)); /* 64K page number */
		IO_Out8(DMA8_COUNT, (NX_U8)(count >> 0));   /* Low byte of count */
		IO_Out8(DMA8_COUNT, (NX_U8)(count >> 8));   /* High byte of count */
		IO_Out8(DMA8_MASK, SB_DMA_8);              /* Enable DMA channel */
	} else {  /* 16 bit sound */
		count -= 2;

		IO_Out8(DMA16_MASK, (SB_DMA_16 & 3) | 0x04);	/* Disable DMA channel */
		
		IO_Out8(DMA16_CLEAR, 0x00);                  /* Clear flip flop */

		/* Set dma mode */
		IO_Out8(DMA16_MODE, (DmaMode == SB16_WRITE_DMA ? DMA16_AUTO_PLAY : DMA16_AUTO_REC));        

		IO_Out8(DMA16_ADDR, (address >> 1) & 0xFF);  /* Low_byte of address */
		IO_Out8(DMA16_ADDR, (address >> 9) & 0xFF);  /* High byte of address */
		IO_Out8(DMA16_PAGE, (address >> 16) & 0xFE); /* 128K page number */
		IO_Out8(DMA16_COUNT, (NX_U8)(count >> 1));    /* Low byte of count */
		IO_Out8(DMA16_COUNT, (NX_U8)(count >> 9));    /* High byte of count */
		IO_Out8(DMA16_MASK, SB_DMA_16 & 3);          /* Enable DMA channel */
	}
}

NX_PRIVATE int dsp_set_size(NX_U32 size)
{
	/* Sanity checks */
	if (size < sb16->min_fragment_size || size > sb16->dma_size / sb16->nr_of_dma_fragments || size % 2 != 0) {
		return -1;
	}

	sb16->dsp_fragment_size = size; 

	return 0;
}

NX_PRIVATE int dsp_set_speed(NX_U32 speed)
{
	if (speed < DSP_MIN_SPEED || speed > DSP_MAX_SPEED)
    {
		return -1;
	}

	/* Soundblaster 16 can be programmed with real sample rates
	* instead of time constants
	*
	* Since you cannot sample and play at the same time
	* we set in- and output rate to the same value 
	*/

	sb16_dsp_write(DSP_INPUT_RATE);		/* set input rate */
	sb16_dsp_write(speed >> 8);			/* high byte of speed */
	sb16_dsp_write(speed);			 		/* low byte of speed */
	sb16_dsp_write(DSP_OUTPUT_RATE);		/* same for output rate */
	sb16_dsp_write(speed >> 8);	
	sb16_dsp_write(speed); 

	sb16->dsp_speed = speed;

	return 0;
}

NX_PRIVATE int dsp_set_stereo(NX_U32 stereo)
{
	if (stereo)
    { 
		sb16->dsp_stereo = 1;
	}
    else
    { 
		sb16->dsp_stereo = 0;
	}

	return 0;
}

NX_PRIVATE int dsp_set_bits(NX_U32 bits) {
	/* Sanity checks */
	if (bits != 8 && bits != 16)
    {
		return NX_EINVAL;
	}

	sb16->dsp_bits = bits; 

	return NX_EOK;
}

NX_PRIVATE int dsp_set_sign(NX_U32 sign)
{
	sb16->dsp_sign = (sign > 0 ? 1 : 0); 

	return NX_EOK;
}

NX_PRIVATE NX_Error sb16_isr(NX_IRQ_Number irqno, void *param)
{
    if (sb16_int()) /* has sb16 intr status */
    {
        /* stop play sound */
        sb16_pause();
        
        sb16->index_r = (sb16->index_r + 1) % SB16_BUF_COUNT;

        /* start transmit if no request */
        if (sb16->index_r != sb16->index_w)
        {
            sb16_transmit(SB16_WRITE_DMA);
        }

        return NX_EOK;
    }
    else
    {
        return NX_EAGAIN;
    }
}

NX_PRIVATE NX_Error SB16_Write(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    char * p = &sb16->data_buf[sb16->index_w * sb16->dma_size];

    if (!len || (len % 2) != 0 || len > sb16->dma_size || !buf)
    {
        return NX_EINVAL;
    }

    while (((sb16->index_w + 1) % SB16_BUF_COUNT) == sb16->index_r)
    {
        NX_ThreadYield();
    }

    sb16->data_len[sb16->index_w] = len;

    NX_CopyFromUser((char *)p, buf, len);

    if (sb16->index_w == sb16->index_r) {
        sb16->index_w = (sb16->index_w + 1) % SB16_BUF_COUNT;
        /* play sound if buf empty */
        sb16_transmit(SB16_WRITE_DMA);
    }
    else
    {
        sb16->index_w = (sb16->index_w + 1) % SB16_BUF_COUNT;
    }

    *outLen = len;

    return NX_EOK;
}

NX_PRIVATE NX_Error SB16_Control(struct NX_Device *device, NX_U32 cmd, void *arg)
{
    NX_U32 val;
    switch (cmd)
    {
    case NX_AUDIO_GET_FMTS:
        val =  NX_AUDIO_FTM_U8 | NX_AUDIO_FTM_S8 | NX_AUDIO_FTM_S16_LE | NX_AUDIO_FTM_U16_LE;
        NX_CopyToUser((char *)arg, (char *)&val, sizeof(val));
        break;
    case NX_AUDIO_SET_FMT:
        NX_CopyFromUser((char *)&val, (char *)arg, sizeof(val));
        switch (val)
        {
        case NX_AUDIO_FTM_U8:
            dsp_set_sign(0);
            dsp_set_bits(8);
            break;
        case NX_AUDIO_FTM_S8:
            dsp_set_sign(1);
            dsp_set_bits(8);
            break;
        case NX_AUDIO_FTM_S16_LE:
            dsp_set_sign(1);
            dsp_set_bits(16);
            break;
        case NX_AUDIO_FTM_U16_LE:
            dsp_set_sign(0);
            dsp_set_bits(16);
            break;
        default:
            return NX_EINVAL;
        }
        break;
    case NX_AUDIO_SET_CHANNELS:
        NX_CopyFromUser((char *)&val, (char *)arg, sizeof(val));
        if (val == 1 || val == 2)
        {
            dsp_set_stereo(val - 1);
        }
        else
        {
            return NX_EINVAL;
        }
        break;
    case NX_AUDIO_SET_SPEED:
        NX_CopyFromUser((char *)&val, (char *)arg, sizeof(val));
        if (dsp_set_speed(val))
        {
            return NX_EINVAL;
        }
        break;
    default:
        return NX_EINVAL;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error SB16_Open(struct NX_Device *device, NX_U32 flags)
{
    sb16_reset();
    sb16->index_w = sb16->index_r = 0;
    sb16->dsp_stereo = DEFAULT_STEREO;
    sb16->dsp_speed = DEFAULT_SPEED; 
    sb16->dsp_bits = DEFAULT_BITS;
    sb16->dsp_sign = DEFAULT_SIGN;
    sb16->running = NX_False;
    return NX_EOK;
}

NX_PRIVATE NX_Error SB16_Close(struct NX_Device *device)
{
    sb16_pause();
    return NX_EOK;
}

NX_PRIVATE NX_DriverOps SB16_DriverOps = {
    .write = SB16_Write,
    .control = SB16_Control,
    .open = SB16_Open,
    .close = SB16_Close,
};

NX_PRIVATE void sb16_device_init(void)
{
    sb16->dsp_stereo = DEFAULT_STEREO;
    sb16->dsp_speed = DEFAULT_SPEED; 
    sb16->dsp_bits = DEFAULT_BITS;
    sb16->dsp_sign = DEFAULT_SIGN;
    sb16->dsp_fragment_size = 0;

    sb16->dma_size = 64 * 1024;
    sb16->nr_of_dma_fragments = 2;
    sb16->min_fragment_size = 1024;

    sb16->dma_phys = 0;

    sb16->data_buf = NX_NULL;
    sb16->index_r = sb16->index_w = 0;
    
    sb16->running = NX_False;
}

NX_PRIVATE NX_Error SB16_Init(void)
{
    NX_Error err;
    NX_U8 major_version;
    NX_U8 minor_version;
    char * dma_buf;
    
    err = sb16_reset();
    if (err != NX_EOK)
    {
        NX_LOG_E("sb16 reset failed!");
        return err;
    }

    sb16_dsp_write(DSP_GET_VERSION);

    major_version = sb16_dsp_read();
    minor_version = sb16_dsp_read();
    NX_LOG_I("sb16 version: %d.%d", major_version, minor_version);

    if (major_version < 4)
    {
        NX_LOG_E("sb16 not support version %d.%d !", major_version, minor_version);
        return NX_EIO;
    }

	mixer_set(MIXER_SET_IRQ, (1 << (SB_IRQ / 2 - 1)));
	mixer_set(MIXER_SET_DMA, (1 << SB_DMA_8 | 1 << SB_DMA_16));

    sb16 = NX_MemAlloc(sizeof(struct sb16_device));
    if (!sb16)
    {
        NX_LOG_E("sb16 alloc device failed!");
        return NX_ENOMEM;
    }
    sb16_device_init();

	sb16->dsp_fragment_size = sb16->dma_size / sb16->nr_of_dma_fragments;

    dma_buf = NX_PageAllocInZone(NX_PAGE_ZONE_DMA, NX_DIV_ROUND_UP(sb16->dma_size, NX_PAGE_SIZE));
    if (!dma_buf)
    {
        NX_LOG_E("sb16 alloc dma buffer failed!");
        NX_MemFree(sb16);
        return NX_ENOMEM;
    }
    
    NX_LOG_I("sb16 dma addr:%p size:%x", dma_buf, sb16->dma_size);
    sb16_set_dma((NX_U32)dma_buf);

    sb16->data_buf = NX_MemAlloc(sb16->dma_size * SB16_BUF_COUNT);
    if (!sb16->data_buf)
    {
        NX_LOG_E("sb16 alloc buffer failed!");
        NX_PageFreeInZone(NX_PAGE_ZONE_DMA, dma_buf);
        NX_MemFree(sb16);
        return NX_ENOMEM;
    }

    if (NX_IRQ_Bind(SB_IRQ, sb16_isr, NX_NULL, "sb16", 0) != NX_EOK)
    {
        NX_LOG_E("sb16 bind irq failed!");
        NX_MemFree(sb16->data_buf);
        NX_PageFreeInZone(NX_PAGE_ZONE_DMA, dma_buf);
        NX_MemFree(sb16);
        return NX_ENOMEM;
    }

    NX_IRQ_Unmask(SB_IRQ);

    return NX_EOK;
}

NX_PRIVATE void SB16_DriverInit(void)
{
    NX_Device *device;
    NX_Error err;

    err = SB16_Init();
    if (err != NX_EOK)
    {
        return;
    }

    NX_Driver *driver = NX_DriverCreate(DRV_NAME, NX_DEVICE_TYPE_SOUND, 0, &SB16_DriverOps);
    if (driver == NX_NULL)
    {
        NX_LOG_E("create driver failed!");
        return;
    }

    if (NX_DriverAttachDevice(driver, DEV_NAME, &device) != NX_EOK)
    {
        NX_LOG_E("attach device %s failed!", DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }

    if (NX_DriverRegister(driver) != NX_EOK)
    {
        NX_LOG_E("register driver %s failed!", DRV_NAME);
        NX_DriverDetachDevice(driver, DEV_NAME);
        NX_DriverDestroy(driver);
        return;
    }
    
    NX_LOG_I("init %s driver success!", DRV_NAME);
}

NX_PRIVATE void SB16_DriverExit(void)
{
    NX_DriverCleanup(DRV_NAME);
}

NX_DRV_INIT(SB16_DriverInit);
NX_DRV_EXIT(SB16_DriverExit);

#endif